#include "socket.h"

Socket::Socket() {
	//Se supone que esto va a andar casi siempre
	this->sockfd = socket(AF_INET, SOCK_STREAM, 0);

}
//Crea un objeto socket a partir de un fd existente
Socket::Socket(int fd) {
	this->sockfd = fd;
}

//Constructor copia
Socket::Socket(const Socket& s) {
	this->sockfd = s.sockfd;

}

Socket::~Socket() {
	close(this->sockfd);
}

//Por si voy a escuchar, bindea mi ip a un puerto para escuchar
int Socket::Bind(int port) const {
	if (port > 65535 || port < 0)
		throw InvalidPortException();
	struct sockaddr_in my_addr;
	my_addr.sin_family = AF_INET;
	// Ordenacion de maquina
	my_addr.sin_port = htons(port);
	// short, Ordenacion de la red
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	memset(&(my_addr.sin_zero), '\0', 8); // Poner a cero el resto
	int bind_result = bind(sockfd, (struct sockaddr *) &my_addr,
			sizeof(struct sockaddr));
	if (bind_result == -1) {
		throw CantBindException();
	}
	return bind_result;
}

int Socket::Connect(const string* server_adress, int server_port) const {
	struct sockaddr_in dest_addr;
	// Guardara la direccion de destino
	dest_addr.sin_family = AF_INET;
	// Ordenacion de maquina
	dest_addr.sin_port = htons(server_port);
	// short, Ordenacion de la red
	dest_addr.sin_addr.s_addr = inet_addr(server_adress->c_str());
	memset(&(dest_addr.sin_zero), '\0', 8);
	int connect_result = connect(sockfd, (struct sockaddr *) &dest_addr,
			sizeof(struct sockaddr));
	if (connect_result == -1) {
		throw ConnectionException();
	}
	return connect_result;
}

int Socket::Listen(int port) const {
	try {
		Bind(port);
	} catch (const CantBindException& e) {
		throw CantListenException();
	}
	int listen_result = listen(sockfd, BACKLOG);
	if (listen_result == -1) {
		throw CantListenException();
	}
	return listen_result;

}

Socket* Socket::Accept() const {
	int new_fd;
	unsigned int sin_size;
	Socket* new_socket = 0;
	struct sockaddr_in their_addr;
	sin_size = sizeof(struct sockaddr_in);
	new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &sin_size);
	if (new_fd != -1)
		new_socket = new Socket(new_fd);
	else {
		throw AcceptingException();
	}
	return new_socket;
}

int Socket::Send(const void* msg, unsigned int len) const {
	unsigned int bytes_sent = 0;
	const char* ptr = (const char*) msg;

	while (bytes_sent < len) {
		int res = send(this->sockfd, &ptr[bytes_sent], len - bytes_sent, 0);
		if (res == 0 || res == -1) {
			throw SendException();
		}
		bytes_sent += res;

	}
	return 0;
}

int Socket::Send(const string* msg) const {
	unsigned int size = msg->size();
	const char* c = msg->c_str();
	return this->Send((const void*) c, size);
}
int Socket::Send(Message* msg) const {
	string str = msg->MsgToString();
	return this->Send(&str);
}

int Socket::Recv(unsigned int size, string* total_received) const {
	unsigned int bytes_remaining = size;
	unsigned int bytes_recv = 0;
	unsigned int size_to_read = 0;
	char* aux_buffer = 0;
	while (bytes_remaining != 0) {

		if (bytes_remaining < buf_length) {
			size_to_read = bytes_remaining;

		} else {
			size_to_read = buf_length;

		}
		//+1 para que me deje ponerle un 0 al final, si lee toda la longitud
		aux_buffer = new char[size_to_read + 1];
		bytes_recv = recv(this->sockfd, (void*) aux_buffer, size_to_read, 0);
		if (bytes_recv != 0) {
			//Pongo el caracter de fin de string un byte despues de lo que lei
			aux_buffer[bytes_recv] = 0;
			//Concateno lo que tengo hasta ahora con lo que lei
			for (unsigned int j = 0; j < bytes_recv; j++) {
				total_received->push_back(aux_buffer[j]);
			}
			//*total_received = *total_received + aux_buffer;
			delete[] aux_buffer;
			bytes_remaining -= bytes_recv;
		} else {
			delete[] aux_buffer;
			throw ConnectionClosedException();
		}

	}
	return bytes_remaining;
}

string Socket::RecvNextMsg() const {
	string buffer;
	string result;
	//Recibo la id del mensaje
	Recv(1, &buffer);
	//Agrego la id al resultado
	result.push_back((int) buffer.at(0));
	buffer.clear();
	//Leo la longitud del resto del mensaje
	Recv(4, &buffer);
	char falso_int[4];
	falso_int[0] = buffer.at(0);
	result.push_back(falso_int[0]);
	falso_int[1] = buffer.at(1);
	result.push_back(falso_int[1]);
	falso_int[2] = buffer.at(2);
	result.push_back(falso_int[2]);
	falso_int[3] = buffer.at(3);
	result.push_back(falso_int[3]);
	uint32_t* size = (uint32_t*) falso_int;
	buffer.clear();
	//Recibo el resto del mensaje
	Recv(ntohl(*size), &buffer);
	for (unsigned int i = 0; i < buffer.size(); i++) {
		result.push_back(buffer.at(i));
	}
	return result;
}

int Socket::Close() {
	int close_result = close(this->sockfd);
	if (close_result == -1) {
		throw CloseException();
	}
	return close_result;
}

int Socket::Shutdown(int opt) {
	int r = 0;
	switch (opt) {
	case 0:
		r = shutdown(this->sockfd, 0);
		break;
	case 1:
		r = shutdown(this->sockfd, 1);
		break;
	case 2:
		r = shutdown(this->sockfd, 2);
		break;
	default:
		r = shutdown(this->sockfd, 2);
		break;
	}
	if (r == -1)
		throw ShutdownException();
	return r;
}

string Socket::GetConnectedInfo() {
	string result;

	struct sockaddr_in adr_inet;/* AF_INET */
	unsigned int len_inet; /* length */
	len_inet = sizeof adr_inet;
	getpeername(this->sockfd, (struct sockaddr *) &adr_inet, &len_inet);

	char buf[100];
	snprintf(buf, 100, "%s:%u", inet_ntoa(adr_inet.sin_addr),
			(unsigned) ntohs(adr_inet.sin_port));
	result = buf;
	unsigned int pos = result.find(':', 0);
	result = result.substr(0, pos);
	return result;
}

